﻿using System;
using System.Collections.Generic;
using UnityEngine;

namespace AI.Graph {


    public enum Direction {

        Output,
        Input

    }

    public enum Capacity {

        Single,
        Multi

    }

    public class ConnectionPort : ScriptableObject {

#region Field

        /// <summary>
        /// The rect of this port.
        /// </summary>
        [SerializeField]
        private Rect m_rect;

        /// <summary>
        /// All connected ports to this port.
        /// </summary>
        [SerializeField]
        private ConnectionPort[] m_connectedPorts = new ConnectionPort[0];

        /// <summary>
        /// The bounded node of this port. 
        /// </summary>
        [SerializeField]
        private Node m_node;

        /// <summary>
        /// The direction of this port.
        /// </summary>
        [SerializeField]
        private Direction m_direction;

        /// <summary>
        /// The amount of connections to this port.
        /// </summary>
        [SerializeField]
        private Capacity m_capacity;

        /// <summary>
        /// all allowed node type names to which the port can be connected. 
        /// </summary>
        [SerializeField]
        private string[] m_allowedConnections;

        /// <summary>
        /// Is the port visible.
        /// </summary>
        [SerializeField]
        internal bool isHidden;

#endregion


#region Properties

        /// <summary>
        /// The direction of this port.
        /// </summary>
        public Direction direction {
            get { return m_direction; }
        }

        /// <summary>
        /// The rect which contains the position and the size of this port.
        /// </summary>
        internal Rect rect {
            get { return m_rect; }
            set { m_rect = value; }
        }

        /// <summary>
        /// The position of this port.
        /// </summary>
        internal Vector2 position {
            get { return m_rect.position; }
            set { m_rect.position = value; }
        }

        /// <summary>
        /// The size of this port.
        /// </summary>
        internal Vector2 size {
            get { return m_rect.size; }
            set { m_rect.size = value; }
        }

        /// <summary>
        /// The bounded node of this port. 
        /// </summary>
        public Node node {
            get { return m_node; }
        }

        /// <summary>
        /// All conntected ports to this port.
        /// </summary>
        public ConnectionPort[] connectedPorts {
            get { return m_connectedPorts; }
        }

        /// <summary>
        /// Is true if the port has at least one connection.
        /// </summary>
        public bool hasConnections {
            get { return m_connectedPorts.Length > 0; }
        }

        /// <summary>
        /// The amount of connections.
        /// </summary>
        public int connectionCount {
            get { return m_connectedPorts.Length; }
        }

#endregion


        void OnEnable () {
            hideFlags = HideFlags.HideInHierarchy;
        }


        internal void Initialize (Direction direction, Capacity capacity, Node node, string name, Type[] allowedConnections) {
            m_direction = direction;
            m_capacity = capacity;
            m_node = node;
            this.name = name;

            if (allowedConnections == null) {
                m_allowedConnections = new string[0];
            }
            else {
                m_allowedConnections = new string[allowedConnections.Length];

                for (int i = 0; i < m_allowedConnections.Length; i++) {
                    m_allowedConnections[i] = allowedConnections[i].AssemblyQualifiedName;
                }
            }
        }


        ///  <summary>
        /// Check if the given port is a valid connection.
        /// An error message will be set if the connection is not valid.
        /// </summary>
        /// <param name="other">Check if this port is a valid connectionPort.</param>
        /// <param name="error">The error message if it's not a valid connection.</param>
        /// <returns>Returns true if the given port is a valid connectionPort.</returns>
        bool IsValidConnection (ConnectionPort other, out string error) {
            if (node == other.node) {
                error = "Cannot connect to the same node.";
                return false;
            }

            if (ArrayUtility.Contains (m_connectedPorts, other)) {
                error = "This Port is already connected with the given.";
                return false;
            }

            if (direction == other.direction) {
                error = "Cannot connect to the same port direction: [" + direction + " -> " + other.direction + "].";
                return false;
            }

            if (m_capacity == Capacity.Single && hasConnections) {
                error = "The port [" + name + "] can only have one connection.";
                return false;
            }

            if (m_allowedConnections.Length > 0) {
                bool isAllowedType = false;
                foreach (string typeName in m_allowedConnections) {
                    Type type = Type.GetType (typeName);

                    if (other.node.GetType () == type || other.node.GetType ().IsSubclassOf (type)) {
                        isAllowedType = true;
                        break;
                    }
                }

                if (!isAllowedType) {
                    error = "[" + other.name + "] is not an allowed node";
                    return false;
                }
            }

            if (!node.IsValidConnection (this, other, out error)) {
                return false;
            }

            error = "";
            return true;
        }


        /// <summary>
        /// Try to connect the given port to this port.
        /// </summary>
        /// <param name="port">The port that should be connected to this.</param>
        /// <param name="error">Error message if the connections is not valid.</param>
        /// <returns>Returns true if the connection was valid.</returns>
        internal bool TryConnectTo (ConnectionPort port, out string error) {
            if (IsValidConnection (port, out error)) {
                if (port.IsValidConnection (this, out error)) {
                    ArrayUtility.Add (ref m_connectedPorts, port);
                    ArrayUtility.Add (ref port.m_connectedPorts, this);
                    return true;
                }
            }

            return false;
        }


        /// <summary>
        /// Remove the given port.
        /// </summary>
        /// <param name="port">The port that will be removed.</param>
        internal void Disconnect (ConnectionPort port) {
            node.OnPortDisconnect (this, port);
            port.node.OnPortDisconnect (port, this);

            ArrayUtility.Remove (ref m_connectedPorts, port);
            ArrayUtility.Remove (ref port.m_connectedPorts, this);
        }


        /// <summary>
        /// Remove all connected ports.
        /// </summary>
        internal void DisconnectAll () {
            List<ConnectionPort> connectedPortsCopy = new List<ConnectionPort> (m_connectedPorts);

            foreach (ConnectionPort port in connectedPortsCopy) {
                Disconnect (port);
            }

            m_connectedPorts = new ConnectionPort[0];
        }


        public void Copy (AIControllerSerializer serializer, ConnectionPort original) {
            m_node = serializer.GetCopyOf (original.m_node);

            m_connectedPorts = new ConnectionPort[original.m_connectedPorts.Length];
            for (int i = 0; i < original.m_connectedPorts.Length; i++) {
                m_connectedPorts[i] = (serializer.GetCopyOf (original.m_connectedPorts[i]));
            }
        }

    }


}